/*

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
/*
 * Created on Aug 11, 2009
 */

package com.bigdata.btree.raba;

import java.io.DataInput;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;

import com.bigdata.util.BytesUtil;

/**
 * Abstract base class implements mutation operators and search. A concrete
 * subclass need only indicate if it is mutable, searchable, or allows nulls
 * by overriding the appropriate methods.  
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
abstract public class AbstractRaba implements IRaba {

    /**
     * The inclusive lower bound of the view.
     */
    protected final int fromIndex;

    /**
     * The exclusive upper bound of the view.
     * <p>
     * Note: This field is NOT final since it is modified by a subclass which
     * permits mutation.
     */
    protected int toIndex;

    /**
     * The maximum #of elements in the view of the backing array.
     */
    protected final int capacity;

    /**
     * The backing array.
     */
    protected final byte[][] a;

    /**
     * Create a view of a byte[][]. All elements in the array are visible in the
     * view.
     * 
     * @param a
     *            The backing byte[][].
     */
    public AbstractRaba(final byte[][] a) {

        this(0/* fromIndex */, a.length/* toIndex */, a.length/* capacity */, a);

    }

    /**
     * Create a view from a slice of a byte[][].
     * 
     * @param fromIndex
     *            The index of the first element in the byte[][] which is
     *            visible in the view (inclusive lower bound).
     * @param toIndex
     *            The index of the first element in the byte[][] beyond the view
     *            (exclusive upper bound).
     * @param capacity
     *            The #of elements which may be used in the view.
     * @param a
     *            The backing byte[][].
     */
    public AbstractRaba(final int fromIndex, final int toIndex,
            final int capacity, final byte[][] a) {

        if (a == null)
            throw new IllegalArgumentException();

        if (fromIndex < 0)
            throw new IllegalArgumentException();

        if (fromIndex > toIndex)
            throw new IllegalArgumentException();

        if (toIndex > a.length)
            throw new IllegalArgumentException();

        if (capacity < toIndex - fromIndex)
            throw new IllegalArgumentException();

        this.fromIndex = fromIndex;

        this.toIndex = toIndex;
        
        this.capacity = capacity;
        
        this.a = a;
        
    }

    @Override
    final public int size() {

        return (toIndex - fromIndex);
        
    }

    @Override
    final public boolean isEmpty() {
        
        return toIndex == fromIndex;
        
    }
    
    @Override
    final public boolean isFull() {

        return size() == capacity();
        
    }
    
    @Override
    final public int capacity() {
        
        return capacity;
        
    }

    final protected boolean rangeCheck(final int index)
            throws IndexOutOfBoundsException {

        if (index < 0 || index >= (toIndex - fromIndex)) {

            throw new IndexOutOfBoundsException("index=" + index
                    + ", fromIndex=" + fromIndex + ", toIndex=" + toIndex);

        }

        return true;

    }
    
    @Override
    final public byte[] get(final int index) {

        assert rangeCheck(index);
        
        return a[fromIndex + index];
        
    }

    @Override
    final public int length(final int index) {

        assert rangeCheck(index);

        final byte[] tmp = a[fromIndex + index];

        if (tmp == null)
            throw new NullPointerException();

        return tmp.length;

    }

    @Override
    final public boolean isNull(final int index) {

        assert rangeCheck(index);

        return a[fromIndex + index] == null;

    }
    
    @Override
    final public int copy(final int index, final OutputStream out) {

        assert rangeCheck(index);

        final byte[] tmp = a[fromIndex + index];

        if (tmp == null)
            throw new NullPointerException();

        try {

            out.write(tmp, 0, tmp.length);
            
        } catch (IOException ex) {
            
            throw new RuntimeException(ex);
            
        }
        
        return tmp.length;
        
    }
    
    @Override
    final public Iterator<byte[]> iterator() {

        return new Iterator<byte[]>() {

        	private int i = fromIndex;

            @Override
            public boolean hasNext() {

                return i < toIndex;

            }

            @Override
            public byte[] next() {

                if (!hasNext())
                    throw new NoSuchElementException();

                return a[i++];

            }

            @Override
            public void remove() {

                if (isReadOnly())
                    throw new UnsupportedOperationException();

                // @todo support remove on the iterator when mutable.
               throw new UnsupportedOperationException();
                
            }

        };

    }
    
    /**
     * @throws UnsupportedOperationException
     *             if the view is read-only.
     */
    protected void assertNotReadOnly() {
        
        if(isReadOnly())
            throw new UnsupportedOperationException();
        
    }

    /**
     * @throws IllegalStateException
     *             unless there is room to store another value.
     */
    protected void assertNotFull() {

        if(toIndex >= fromIndex + a.length) {
            
            throw new IllegalStateException();
            
        }

    }

    /**
     * @param a
     *            A byte[] to be inserted or set on the {@link IRaba}.
     * 
     * @throws IllegalArgumentException
     *             if the <code>byte[]</code> is <code>null</code> and the
     *             implementation does not permit <code>null</code>s to be
     *             stored.
     */
    protected void assertNullAllowed(final byte[] a) {
        
        if (a == null && isKeys()) {

            throw new IllegalArgumentException();
            
        }
        
    }
    
    @Override
    public void set(final int index, final byte[] key) {
        
        assertNotReadOnly();
        
        assert rangeCheck(index);

        assertNullAllowed(key);

        a[fromIndex + index] = key;
        
    }

    @Override
    public int add(final byte[] key) {

        assertNotReadOnly();

        assertNotFull();

        assertNullAllowed(key);
        
        assert toIndex < fromIndex + capacity;

        a[toIndex++] = key;
        
        return (toIndex - fromIndex);
        
    }
    
    @Override
    public int add(final byte[] key, final int off, final int len) {

        assertNotReadOnly();        

        assertNotFull();

        assertNullAllowed(key);

        final byte[] b = new byte[len];

        System.arraycopy(key, off, b, 0, len);
        
//        for (int i = 0; i < len; i++) {
//
//            b[i] = key[off + i];
//
//        }

        a[toIndex++] = b;

        return (toIndex - fromIndex);

    }
    
    @Override
    public int add(final DataInput in, final int len) throws IOException {

        assertNotReadOnly();
        
        assertNotFull();

        final byte[] b = new byte[len];

        in.readFully(b, 0, len);

        a[toIndex++] = b;

        return (toIndex - fromIndex);

    }

    @Override
    public int search(final byte[] searchKey) {

        if (!isKeys()) {

            throw new UnsupportedOperationException();
            
        }

        return BytesUtil.binarySearch(a, 0/* base */, size()/* nmem */,
                searchKey);
        
    }
    
    @Override
    public String toString() {

        return toString(this);
        
    }
    
    /**
     * If {@link IRaba#isKeys()} is <code>true</code> then represents the
     * elements as <code>unsigned byte[]</code>s.  Otherwise represents the
     * elements as <code>signed byte[]</code>s.
     */
    static public String toString(final IRaba raba) {

        final StringBuilder sb = new StringBuilder();

        final boolean isKeys = raba.isKeys();
        
        sb.append(raba.getClass().getName());
        sb.append("{ capacity=" + raba.capacity());
        sb.append(", size=" + raba.size());
        sb.append(", isKeys=" + isKeys);
        sb.append(", isReadOnly=" + raba.isReadOnly());
        sb.append(", [\n");

        int i = 0;
        for(byte[] a : raba) {

            if (i > 0)
                sb.append(",\n");

            if (a == null) {

                sb.append("null");

            } else {

                if(isKeys) {
                    
                    // representation as unsigned byte[].
                    sb.append(BytesUtil.toString(a));
                    
                } else {
                    
                    // representation as signed byte[].
                    sb.append(Arrays.toString(a));
                    
                }

            }
            
            i++;

        }

        sb.append("]}");
        
        return sb.toString();

    }

    /**
     * Resize the buffer, copying up to <i>n</i> references to the existing data
     * into a new view backed by a new byte[][]. <code>fromIndex</code> will be
     * zero in the new view.
     * <p>
     * This method requires a public constructor with the following signature:
     * 
     * <pre>
     * ctor(byte[][])
     * </pre>
     * 
     * @param n
     *            The size of the new buffer.
     * 
     * @return The new view, backed by a new byte[][].
     * 
     * @throws IllegalArgumentException
     *             if <i>n</i> is negative.
     * 
     * @throws RuntimeException
     *             if the {@link AbstractRaba} instance does not declare an
     *             appropriate ctor.
     * 
     * @todo redefine as slice() (view onto the same data) and copy(int n)?
     */
    public AbstractRaba resize(final int n) {

        assertNotReadOnly();
        
        if (n < 0)
            throw new IllegalArgumentException();

        // #of entries in the source.
        final int m = size();

        // #of entries to be copied into the new buffer.
        final int p = Math.min(m, n);

        // new backing array sized to [n].
        final byte[][] b = new byte[n][];

        // copy references to the new buffer.
        System.arraycopy(a, fromIndex, b, 0, p);

        try {
            
            final Constructor<? extends AbstractRaba> ctor = getClass()
                    .getConstructor(new Class[] { byte[].class });

            return ctor.newInstance(new Object[] { a });

        } catch (Exception ex) {

            throw new RuntimeException(ex);

        }

    }

}
